﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using System.Xml;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;
using DTSExecResult = Microsoft.SqlServer.Dts.Runtime.DTSExecResult;
using DTSProductLevel = Microsoft.SqlServer.Dts.Runtime.DTSProductLevel;
using VariableDispenser = Microsoft.SqlServer.Dts.Runtime.VariableDispenser;
using SSISFileWatcherTask100.FileWatch;

namespace SSISFileWatcherTask100.SSIS
{
    [DtsTask(
        DisplayName = "File Watcher Task",
        UITypeName = "SSISFileWatcherTask100.SSISFileWatcherTaskUIInterface" +
        ",SSISFileWatcherTask100," +
        "Version=1.0.0.15," +
        "Culture=Neutral," +
        "PublicKeyToken=6ca0c85d7d8af042",
        IconResource = "SSISFileWatcherTask100.FileWatcher.ico",
        TaskContact = "cosmin.vlasiu@gmail.com",
        RequiredProductLevel = DTSProductLevel.None
        )]
    public class SSISFileWatcherTask : Task, IDTSComponentPersist
    {
        #region Constructor
        public SSISFileWatcherTask()
        {
        }

        #endregion

        #region Public Properties

        [Category("File Watching"), Description("InputFolderPath: Folder to monitor")]
        public string InputFolderPath { get; set; }
        [Category("File Watching"), Description("InputType: From FILE Connection or Variable/Direct Input")]
        public string InputType { get; set; }
        [Category("File Watching"), Description("FileFilter : *.* | *.csv | *.txt")]
        public string FileFilter { get; set; }
        [Category("File Watching"), Description("FireOn: OnCreate | OnDelete | OnRename")]
        public string FireOn { get; set; }
        [Category("File Watching"), Description("IncludeSubdirectories: TRUE/FALSE")]
        public string IncludeSubdirectories { get; set; }
        [Category("File Watching"), Description("Timeout / 0 = No Timeout")]
        public string Timeout { get; set; }
        [Category("File Watching"), Description("Output Variable Name")]
        public string OutputVariableName { get; set; }
        #endregion

        #region Private Properties

        Variables _vars = null;

        #endregion

        #region Validate

        /// <summary>
        /// Validate local parameters
        /// </summary>
        public override DTSExecResult Validate(Connections connections, VariableDispenser variableDispenser,
                                               IDTSComponentEvents componentEvents, IDTSLogging log)
        {
            bool isBaseValid = true;

            Executing(variableDispenser, componentEvents);

            return isBaseValid ? DTSExecResult.Success : DTSExecResult.Failure;
        }

        #endregion

        #region Execute

        /// <summary>
        /// This method is a run-time method executed dtsexec.exe
        /// </summary>
        /// <param name="connections"></param>
        /// <param name="variableDispenser"></param>
        /// <param name="componentEvents"></param>
        /// <param name="log"></param>
        /// <param name="transaction"></param>
        /// <returns></returns>
        public override DTSExecResult Execute(Connections connections, VariableDispenser variableDispenser, IDTSComponentEvents componentEvents, IDTSLogging log, object transaction)
        {
            Executing(variableDispenser, componentEvents);

            return base.Execute(connections, variableDispenser, componentEvents, log, transaction);
        }

        private void Executing(VariableDispenser variableDispenser, IDTSComponentEvents componentEvents)
        {
            bool refire = false;
            GetNeededVariables(variableDispenser);

            var timeout = 0;
            Int32.TryParse(EvaluateExpression(Timeout, variableDispenser).ToString(), out timeout);

            try
            {
                componentEvents.FireInformation(0, "SSISFileWatcher", String.Format("Folder to watch: {0} ", EvaluateExpression(InputFolderPath, variableDispenser)), string.Empty, 0, ref refire);
                componentEvents.FireInformation(0, "SSISFileWatcher", String.Format("Applied filter: {0} ", EvaluateExpression(FileFilter, variableDispenser)), string.Empty, 0, ref refire);
                componentEvents.FireInformation(0, "SSISFileWatcher", String.Format("Fire on: {0} ", FireOn), string.Empty, 0, ref refire);
                componentEvents.FireInformation(0, "SSISFileWatcher", String.Format("Include Subdirectories: {0} ", IncludeSubdirectories), string.Empty, 0, ref refire);
                componentEvents.FireInformation(0, "SSISFileWatcher", String.Format("TimeOut: {0} ", timeout), string.Empty, 0, ref refire);

                var target = new FileWatcherCore(componentEvents,
                                                 EvaluateExpression(InputFolderPath, variableDispenser).ToString(),
                                                 EvaluateExpression(FileFilter, variableDispenser).ToString(),
                                                 IncludeSubdirectories == Keys.TRUE,
                                                 FireOn,
                                                 timeout);

                componentEvents.FireInformation(0, "SSISFileWatcher", "Start Watching", string.Empty, 0, ref refire);
                target.StartWatching();

                var output = GetVariableFromNamespaceContext(OutputVariableName);
                componentEvents.FireInformation(0, "SSISFileWatcher", String.Format("Output value for {0} is: {1} ", output, target.FullFileName), string.Empty, 0, ref refire);
                _vars[output].Value = target.FullFileName;

                componentEvents.FireInformation(0, "SSISFileWatcher", "Stop Watching...", string.Empty, 0, ref refire);
                target.StopWatching();
                componentEvents.FireInformation(0, "SSISFileWatcher", "File Watcher Stopped...", string.Empty, 0, ref refire);
            }
            catch (Exception ex)
            {
                componentEvents.FireError(0, "SSISFileWatcher", string.Format("Error : {0} - {1} - {2}", ex.Message, ex.Source, ex.StackTrace), "", 0);
            }
            finally
            {
                if (_vars != null)
                    if (_vars.Locked != null)
                        if (_vars.Locked)
                            _vars.Unlock();
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Gets the variable from namespace context.
        /// </summary>
        /// <param name="ssisVariable">The ssis variable.</param>
        /// <returns></returns>
        private static string GetVariableFromNamespaceContext(string ssisVariable)
        {
            return ssisVariable.Split(new[] { "::" }, StringSplitOptions.RemoveEmptyEntries)[1].Replace("[", string.Empty).Replace("]", string.Empty).Replace("@", string.Empty);
        }

        private void GetNeededVariables(VariableDispenser variableDispenser)
        {
            var lockForReadWrite = new List<string>();

            {
                var mappedParams = InputFolderPath.Split(new[] { "@" }, StringSplitOptions.RemoveEmptyEntries);
                LockForRead(variableDispenser, lockForReadWrite, mappedParams);
            }

            {
                var mappedParams = FileFilter.Split(new[] { "@" }, StringSplitOptions.RemoveEmptyEntries);
                LockForRead(variableDispenser, lockForReadWrite, mappedParams);
            }

            {
                var mappedParams = Timeout.Split(new[] { "@" }, StringSplitOptions.RemoveEmptyEntries);
                LockForRead(variableDispenser, lockForReadWrite, mappedParams);
            }

            {
                var mappedParams = OutputVariableName.Split(new[] { "@" }, StringSplitOptions.RemoveEmptyEntries);
                LockForWrite(variableDispenser, lockForReadWrite, mappedParams);
            }
        }

        private static void LockForRead(VariableDispenser variableDispenser, List<string> lockForReadWrite, IEnumerable<string> mappedParams)
        {
            foreach (string t in mappedParams)
            {
                try
                {
                    string param = t.Split(new[] { "::" }, StringSplitOptions.RemoveEmptyEntries)[1].Replace("[", string.Empty).Replace("]", string.Empty).Replace("@", string.Empty);
                    if (!IsVariableInLockForReadOrWrite(lockForReadWrite, param))
                        variableDispenser.LockForRead(param);
                }
                catch (Exception)
                {
                    //We will continue...
                }
            }
        }

        private static void LockForWrite(VariableDispenser variableDispenser, List<string> lockForReadWrite, IEnumerable<string> mappedParams)
        {
            foreach (string t in mappedParams)
            {
                try
                {
                    string param = t.Split(new[] { "::" }, StringSplitOptions.RemoveEmptyEntries)[1].Replace("[", string.Empty).Replace("]", string.Empty).Replace("@", string.Empty);
                    if (!IsVariableInLockForReadOrWrite(lockForReadWrite, param))
                        variableDispenser.LockForWrite(param);
                }
                catch (Exception)
                {
                    //We will continue...
                }
            }
        }

        /// <summary>
        /// Determines whether [is variable in lock for read or write] [the specified lock for read].
        /// </summary>
        /// <param name="lockForReadWrite"></param>
        /// <param name="variable">The variable.</param>
        /// <returns>
        /// 	<c>true</c> if [is variable in lock for read or write] [the specified lock for read]; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsVariableInLockForReadOrWrite(List<string> lockForReadWrite, string variable)
        {
            bool retVal = lockForReadWrite.Contains(variable);

            if (!retVal)
            {
                lockForReadWrite.Add(variable);
            }

            return retVal;
        }

        /// <summary>
        /// This method evaluate expressions like @([System::TaskName] + [System::TaskID]) or any other operation created using 
        /// ExpressionBuilder
        /// </summary>
        /// <param name="mappedParam"></param>
        /// <param name="variableDispenser"></param>
        /// <returns></returns>
        private static object EvaluateExpression(string mappedParam, VariableDispenser variableDispenser)
        {
            object variableObject = null;
            try
            {
                var expressionEvaluatorClass = new ExpressionEvaluatorClass
                {
                    Expression = mappedParam
                };

                expressionEvaluatorClass.Evaluate(DtsConvert.GetExtendedInterface(variableDispenser), out variableObject, false);
            }
            catch
            {
                variableObject = mappedParam;
            }

            return variableObject;
        }

        #endregion

        #region Implementation of IDTSComponentPersist

        void IDTSComponentPersist.SaveToXML(XmlDocument doc, IDTSInfoEvents infoEvents)
        {
            try
            {
                XmlElement taskElement = doc.CreateElement(string.Empty, "SSISFileWatcher", string.Empty);

                XmlAttribute inputFolderPath = doc.CreateAttribute(string.Empty, Keys.InputFolderPath, string.Empty);
                inputFolderPath.Value = InputFolderPath;

                XmlAttribute inputType = doc.CreateAttribute(string.Empty, Keys.InputType, string.Empty);
                inputType.Value = InputType;

                XmlAttribute fileFilter = doc.CreateAttribute(string.Empty, Keys.FileFilter, string.Empty);
                fileFilter.Value = FileFilter;

                XmlAttribute includeSubdirectories = doc.CreateAttribute(string.Empty, Keys.IncludeSubdirectories, string.Empty);
                includeSubdirectories.Value = IncludeSubdirectories;

                XmlAttribute timeout = doc.CreateAttribute(string.Empty, Keys.Timeout, string.Empty);
                timeout.Value = Timeout;

                XmlAttribute outputVariableName = doc.CreateAttribute(string.Empty, Keys.OutputVariableName, string.Empty);
                outputVariableName.Value = OutputVariableName;

                XmlAttribute fireOn = doc.CreateAttribute(string.Empty, Keys.FireOn, string.Empty);
                fireOn.Value = FireOn;

                taskElement.Attributes.Append(inputFolderPath);
                taskElement.Attributes.Append(inputType);
                taskElement.Attributes.Append(fileFilter);
                taskElement.Attributes.Append(includeSubdirectories);
                taskElement.Attributes.Append(timeout);
                taskElement.Attributes.Append(outputVariableName);
                taskElement.Attributes.Append(fireOn);

                doc.AppendChild(taskElement);
            }
            catch (Exception exception)
            {
                MessageBox.Show("There is an error trying to save components properties: " + exception.Message);
            }

        }

        void IDTSComponentPersist.LoadFromXML(XmlElement node, IDTSInfoEvents infoEvents)
        {
            if (node.Name != "SSISFileWatcher")
            {
                throw new Exception("Unexpected task element when loading task.");
            }

            try
            {
                InputFolderPath = node.Attributes.GetNamedItem(Keys.InputFolderPath).Value;
                FireOn = node.Attributes.GetNamedItem(Keys.FireOn).Value;
                InputType = node.Attributes.GetNamedItem(Keys.InputType).Value;
                FileFilter = node.Attributes.GetNamedItem(Keys.FileFilter).Value;
                IncludeSubdirectories = node.Attributes.GetNamedItem(Keys.IncludeSubdirectories).Value;
                Timeout = node.Attributes.GetNamedItem(Keys.Timeout).Value;
                OutputVariableName = node.Attributes.GetNamedItem(Keys.OutputVariableName).Value;
            }
            catch (Exception exception)
            {
                MessageBox.Show("The component is corrupted! Impossible to load the properties: " + exception.Message);
            }
        }

        #endregion
    }
}

